package com.ulisesbocchio.jasyptspringboot.configuration; import com.ulisesbocchio.jasyptspringboot.EncryptablePropertyResolver; import com.ulisesbocchio.jasyptspringboot.annotation.EncryptablePropertySources; import com.ulisesbocchio.jasyptspringboot.wrapper.EncryptableEnumerablePropertySourceWrapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.env.PropertySourcesLoader; import org.springframework.context.ApplicationContextException; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.stream.Stream; import static com.ulisesbocchio.jasyptspringboot.configuration.EncryptablePropertyResolverConfiguration.RESOLVER_BEAN_PLACEHOLDER; /** * @author Ulises Bocchio */ @Slf4j public class EncryptablePropertySourceBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { ConfigurableEnvironment env = beanFactory.getBean(ConfigurableEnvironment.class); ResourceLoader ac = new DefaultResourceLoader(); EncryptablePropertyResolver resolver = beanFactory.getBean(env.resolveRequiredPlaceholders(RESOLVER_BEAN_PLACEHOLDER), EncryptablePropertyResolver.class); MutablePropertySources propertySources = env.getPropertySources(); Stream<AnnotationAttributes> encryptablePropertySourcesMetadata = getEncryptablePropertySourcesMetadata(beanFactory); encryptablePropertySourcesMetadata.forEach(eps -> loadEncryptablePropertySource(eps, env, ac, resolver, propertySources)); } private static void loadEncryptablePropertySource(AnnotationAttributes encryptablePropertySource, ConfigurableEnvironment env, ResourceLoader resourceLoader, EncryptablePropertyResolver resolver, MutablePropertySources propertySources) throws BeansException { try { PropertySource ps = createPropertySource(encryptablePropertySource, env, resourceLoader, resolver); if (ps != null) { propertySources.addLast(ps); log.info("Created Encryptable Property Source '{}' from locations: {}", ps.getName(), Arrays.asList(encryptablePropertySource.getStringArray("value"))); } else { log.info("Ignoring NOT FOUND Encryptable Property Source '{}' from locations: {}", encryptablePropertySource.getString("name"), Arrays.asList(encryptablePropertySource.getStringArray("value"))); } } catch (Exception e) { throw new ApplicationContextException("Exception Creating PropertySource", e); } } private static PropertySource createPropertySource(AnnotationAttributes attributes, ConfigurableEnvironment environment, ResourceLoader resourceLoader, EncryptablePropertyResolver resolver) throws Exception { String name = attributes.getString("name"); String[] locations = attributes.getStringArray("value"); boolean ignoreResourceNotFound = attributes.getBoolean("ignoreResourceNotFound"); CompositePropertySource compositePropertySource = new CompositePropertySource(generateName(name)); Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required"); for (String location : locations) { String resolvedLocation = environment.resolveRequiredPlaceholders(location); Resource resource = resourceLoader.getResource(resolvedLocation); if (!resource.exists() && !ignoreResourceNotFound) { throw new IllegalStateException("Resource not found: " + location); } PropertySourcesLoader loader = new PropertySourcesLoader(); PropertySource propertySource = loader.load(resource, resolvedLocation, null); if (propertySource != null) { compositePropertySource.addPropertySource(propertySource); } } return new EncryptableEnumerablePropertySourceWrapper<>(compositePropertySource, resolver); } private static String generateName(String name) { return !StringUtils.isEmpty(name) ? name : "EncryptedPropertySource#" + System.currentTimeMillis(); } private static Stream<AnnotationAttributes> getEncryptablePropertySourcesMetadata(ConfigurableListableBeanFactory beanFactory) { Stream<AnnotationAttributes> source = getBeanDefinitionsForAnnotation(beanFactory, com.ulisesbocchio.jasyptspringboot.annotation.EncryptablePropertySource.class); Stream<AnnotationAttributes> sources = getBeanDefinitionsForAnnotation(beanFactory, EncryptablePropertySources.class) .flatMap(map -> Arrays.stream((AnnotationAttributes[]) map.get("value"))); return Stream.concat(source, sources); } private static Stream<AnnotationAttributes> getBeanDefinitionsForAnnotation(ConfigurableListableBeanFactory bf, Class<? extends Annotation> annotation) { return Arrays.stream(bf.getBeanNamesForAnnotation(annotation)) .map(bf::getBeanDefinition) .filter(bd -> bd instanceof AnnotatedBeanDefinition) .map(bd -> (AnnotatedBeanDefinition) bd) .map(AnnotatedBeanDefinition::getMetadata) .filter(md -> md.hasAnnotation(annotation.getName())) .map(md -> (AnnotationAttributes) md.getAnnotationAttributes(annotation.getName())); } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } }